/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "stdafx.h"

#include "bitmaputil.h"
#include "DataCallback.h"

//#define _TEST


CWiaDataCallback::CWiaDataCallback( ImageSite* pImageSite, PFNPROGRESSCALLBACK pfnProgressCallback, PVOID pProgressCallbackParam, LONG* plCount )
	: m_refCount( 1 ),
	m_isBmp( false ),
	m_headerSize( 0 ),
	m_dataSize( 0 ),
	m_plCount( plCount ),
	m_pBuffer( NULL ),
	m_pImageSite( pImageSite ),
	m_imageSize( 0 )
{
	m_pfnProgressCallback = pfnProgressCallback;
	m_pProgressCallbackParam = pProgressCallbackParam;
}

// IUnknown interface implementation

STDMETHODIMP CWiaDataCallback::QueryInterface( REFIID iid, LPVOID *ppvObj )
{
	if ( ppvObj == NULL )
	{
		return E_POINTER;
	}

	if ( iid == IID_IUnknown )
	{
		*ppvObj = (IUnknown*) this;
	}
	else if ( iid == IID_IWiaDataCallback )
	{
		*ppvObj = ( IWiaDataCallback* ) this;
	}
	else
	{
		*ppvObj = NULL;
		return E_NOINTERFACE;
	}

	AddRef();

	return S_OK;
}

STDMETHODIMP_(ULONG) CWiaDataCallback::AddRef()
{
	return InterlockedIncrement( &m_refCount );
}

STDMETHODIMP_(ULONG) CWiaDataCallback::Release()
{
	LONG refCount = InterlockedDecrement( &m_refCount );

	if ( refCount == 0 )
	{
		delete this;
	}

	return refCount;
}

// IWiaDataCallback interface implementation

// Called periodically during data transfers.
STDMETHODIMP CWiaDataCallback::BandedDataCallback( LONG lMessage, LONG lStatus, LONG lPercentComplete, LONG lOffset, LONG lLength, LONG, LONG, BYTE* pbBuffer )
{
	HRESULT hr = 0;

	switch ( lMessage )
	{
	case IT_MSG_DATA_HEADER: // tries to allocate memory for the image if the size is given in the header
		{
#ifdef _TEST
			DWORD ticks = GetTickCount();
#endif
			PWIA_DATA_CALLBACK_HEADER pHeader = ( PWIA_DATA_CALLBACK_HEADER ) pbBuffer;

			// determine if this is a BMP transfer

			m_isBmp = pHeader->guidFormatID == WiaImgFmt_MEMORYBMP || pHeader->guidFormatID == WiaImgFmt_BMP;

			// allocate memory for the image if the size is given in the header

			if ( m_isBmp && pHeader && pHeader->lBufferSize )
			{
				if ( !reallocBuffer( m_headerSize + pHeader->lBufferSize ) )
					return E_OUTOFMEMORY;
			}

#ifdef _TEST
			DWORD ticks2 = GetTickCount();
			printf("IT_MSG_DATA_HEADER: in (%i) out(%i) span(%i)\n", ticks, ticks2, ticks2 - ticks);
#endif
			break;
		}

	case IT_MSG_DATA: // adjusts the stream size and copies the new data to the stream.
		{
#ifdef _TEST
			DWORD ticks = GetTickCount();
#endif
			if ( !m_isBmp ) break;

			// if the buffer is not allocated yet and this is the first block, and the transferred image is in BMP format, allocate the buffer according to the size information in the bitmap header

			if ( lOffset == 0 ) // if pdata != NULL
			{
				LONG bufferSize = GetBitmapSize( pbBuffer );

				if ( bufferSize != 0 )
				{
					if ( !reallocBuffer( m_headerSize + bufferSize ) )
					{
						return E_OUTOFMEMORY;
					}
				}
			}

			if ( m_headerSize + lOffset + lLength < 0 )
			{
				return E_OUTOFMEMORY;
			}

			// if the transfer goes past the buffer, try to expand it
			if ( m_headerSize + lOffset + lLength > m_dataSize )
			{
				if ( !reallocBuffer(  m_headerSize + lOffset + lLength ) )
				{
					return E_OUTOFMEMORY;
				}
			}

			m_imageSize += lLength;

			// copy the transfer buffer
			copyToBuffer( m_headerSize + lOffset, pbBuffer, lLength );

#ifdef _TEST
			DWORD ticks2 = GetTickCount();
			printf("IT_MSG_DATA: in (%i) out(%i) span(%i)\n", ticks, ticks2, ticks2 - ticks);
#endif
			break;
		}

	case IT_MSG_STATUS:
		{
			// Invoke the callback function

			/*hr = m_pfnProgressCallback(lStatus, lPercentComplete, m_pProgressCallbackParam);

			if (FAILED(hr) || hr == S_FALSE)
			{
			return hr;
			}*/

			break;
		}

	case IT_MSG_TERMINATION:
	case IT_MSG_NEW_PAGE: // for BMP images, calculates the image height if it is not given in the image header, and stores this stream in the successfully transferred images array
		{
#ifdef _TEST
			DWORD ticks = GetTickCount();
#endif
			if ( m_isBmp )
			{
				/*IaModelImage modelImage;
				modelImage.dataLength = m_imageSize;
				modelImage.pImageData = m_pBuffer; TODO*/

				if ( m_pBuffer ) // if it's not termination
				{
					//m_pImageSite->imageReady( modelImage );
				}

				// set the buffer to NULL so that a new buffer can be allocated
				m_pBuffer = NULL;
				m_imageSize = 0;
				m_dataSize = 0;

#ifdef _TEST
			DWORD ticks2 = GetTickCount();
			printf("IT_MSG_NEW_PAGE: in (%i) out(%i) span(%i)\n", ticks, ticks2, ticks2 - ticks);
#endif
			}
			break;
		}
	}

	return S_OK;
}

bool CWiaDataCallback::reallocBuffer( size_t size )
{
	if ( !m_pBuffer || ( ( LONG ) size ) > m_dataSize )
	{
		m_pBuffer = ( byte_t* ) ::realloc( m_pBuffer, size );
		if ( !m_pBuffer )
			return false;

		memset( m_pBuffer, 0, size );

		m_dataSize = ( LONG ) size;
	}

	return true;
}

bool CWiaDataCallback::copyToBuffer( uint_t offset, const void* pBuffer, uint_t size )
{
	memcpy( m_pBuffer + offset, pBuffer, size );

	return true;
}
